"""
LinkedIn客户端 - 简化版本
整合了浏览器操作、数据采集、服务管理等功能
"""
import asyncio
import uuid
import os
from typing import Dict, Any, Optional, List
from datetime import datetime
from playwright.async_api import async_playwright, Browser, BrowserContext, Page
from pydantic import BaseModel, Field
from core.utils.logger_manager import LoggerManager

from core.chrome_manager import get_chrome_manager
from core.utils.chrome_finder import ChromeFinder
from config.settings import settings
import random, asyncio
from database.connection import db_manager
from database.repositories import UserProfileRepository
from core.cookies_manager import CookiesManager
import traceback
class LinkedInProfile(BaseModel):
    """LinkedIn用户档案模型"""
    profile_id: str = Field(..., description="LinkedIn用户ID")
    name: Optional[str] = Field(None, description="用户名/昵称")
    title: Optional[str] = Field(None, description="职位")
    headline: Optional[str] = Field(None, description="个人简介/标题")
    profile_url: Optional[str] = Field(None, description="个人主页链接")
    avatar_url: Optional[str] = Field(None, description="头像链接")
    company: Optional[str] = Field(None, description="公司")
    industry: Optional[str] = Field(None, description="行业")
    experience: Optional[Any] = Field(None, description="工作经历")
    education: Optional[Any] = Field(None, description="教育背景")
    skills: Optional[Any] = Field(None, description="技能")
    summary: Optional[str] = Field(None, description="个人总结")
    phone: Optional[Any] = Field(None, description="电话号码")
    website: Optional[Any] = Field(None, description="网站链接")
    address: Optional[Any] = Field(None, description="地址")
    email: Optional[str] = Field(None, description="邮箱")
    platform: Optional[str] = Field("linkedin", description="平台")


class LinkedInSearchParams(BaseModel):
    """LinkedIn搜索参数"""
    keyword: str = Field(..., description="搜索关键词")
    location: Optional[str] = Field(None, description="位置")
    max_pages: int = Field(1, description="最大页数")
    results_per_page: int = Field(10, description="每页结果数")


class LinkedInClient:
    """LinkedIn客户端 - 整合所有功能"""
    
    def __init__(self, debug_port: int = 9222,cookie:List[Dict[str, Any]]=None,task_id:str=None):
        self.logger = LoggerManager.get_task_logger("linkedin",task_id)
    
        self.chrome_manager = get_chrome_manager()
        self.browser: Optional[Browser] = None
        self.context: Optional[BrowserContext] = None
        self.page: Optional[Page] = None
        self.is_logged_in = False
        self.cookie=cookie
        self.current_browser_id: Optional[str] = None
        self.debug_port=debug_port
        self.cookies_manager = CookiesManager()
        self.task_id=task_id
    async def start_browser(self, headless: bool = False, proxy_config: Dict[str, str] = None) -> bool:
        """启动浏览器 - 使用新的浏览器管理逻辑"""
        try:
            
            # 连接浏览器
            playwright = await async_playwright().start()
          
            self.browser = await playwright.chromium.connect_over_cdp(f"http://localhost:{self.debug_port}")
            
            # 获取浏览器上下文和页面
            if self.browser.contexts:
                self.context = self.browser.contexts[0]
                if self.cookie:
                    await self.context.add_cookies(self.cookie)
                if self.context.pages:
                    self.page = self.context.pages[0]
                    self.logger.info("使用现有的page")
                else:
                    self.page = await self.context.new_page()
                    self.logger.info("创建新的page")
            else:
                # 如果没有上下文，创建新的
                self.context = await self.browser.new_context(
                    viewport={'width': 1920, 'height': 1080},
                    user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
                )
                if self.cookie:
                    await self.context.add_cookies(self.cookie)
                self.page = await self.context.new_page()
                self.logger.info("创建新的context和page")
            
            self.logger.info(f"LinkedIn浏览器启动成功，浏览器ID: {self.current_browser_id}")
            return True
            
        except Exception as e:
          
            self.logger.error(f"启动浏览器失败: {str(e)}\n{traceback.format_exc()}")
            return False
    
    async def close_browser(self):
        """关闭浏览器连接"""
        try:
            if self.page:
                await self.page.close()
            if self.context:
                await self.context.close()
            # CDP连接不需要关闭browser，只关闭Playwright连接
            self.logger.info("LinkedIn浏览器连接已关闭")
        except Exception as e:
            self.logger.warning(f"关闭浏览器连接失败: {str(e)}")
    
    async def login(self, wait_timeout: int = 600) -> bool:
        """等待用户手动登录LinkedIn"""
        try:
            if not await self.start_browser():
                return False
            
            # 访问登录页面
            await self.page.goto("https://www.linkedin.com/login")
            await self.page.wait_for_load_state("domcontentloaded")
            
            self.logger.info("请在浏览器中手动登录LinkedIn，等待登录成功...")
            
            # 等待用户登录成功，最长等待10分钟
            start_time = asyncio.get_event_loop().time()
            
            while True:
                try:
                    # 检查当前URL是否为目标页面
                    current_url = self.page.url
                    
                    if "https://www.linkedin.com/feed/" in current_url:
                        self.is_logged_in = True
                        self.cookie=await self.context.cookies()
                        self.cookies_manager.save_cookies("linkedin", self.cookie)
                        self.logger.info("LinkedIn登录成功！")
                        return True
                    
                    # 检查是否超时
                    elapsed_time = asyncio.get_event_loop().time() - start_time
                    if elapsed_time > wait_timeout:
                        self.logger.error(f"登录超时，等待时间超过{wait_timeout}秒")
                        return False
                    
                    # 等待1秒后再次检查
                    await asyncio.sleep(1)
                    
                except Exception as e:
                    self.logger.warning(f"检查登录状态时出错: {str(e)}")
                    await asyncio.sleep(1)
                    continue
                
        except Exception as e:
            self.logger.error(f"LinkedIn登录失败: {str(e)}")
            return False
    
    async def check_login_status(self, wait_timeout: int = 600) -> bool:
        """检查登录状态 - 循环检测直到登录成功或超时"""
        try:
            if not self.page:
                if self.context and self.context.pages:
                    self.page = self.context.pages[0]
                else:
                    return False
            self.logger.info("开始检测LinkedIn登录状态...")
                    # 等待1秒后再次检测
            try:
                await self.page.goto("https://www.linkedin.com")
                await asyncio.sleep(3)
                await self.page.wait_for_load_state("domcontentloaded", timeout=10000)  # 10秒超时
            except Exception:
                # 如果domcontentloaded超时，继续执行
                pass   
            # 等待用户登录成功，最长等待10分钟
            start_time = asyncio.get_event_loop().time()
            
            while True:
                try:
                              
                    # 检查当前URL是否为目标页面
                    current_url = self.page.url
                    
                    if await self.page.query_selector('[role="region"]'):
                        self.is_logged_in = True
                        self.logger.info("检测到登录成功！")
                        self.cookie=await self.context.cookies()
                        self.cookies_manager.save_cookies("linkedin",self.cookie)
                        return True
                    
                    # 检查是否超时
                    elapsed_time = asyncio.get_event_loop().time() - start_time
                    if elapsed_time > wait_timeout:
                        self.logger.error(f"登录检测超时，等待时间超过{wait_timeout}秒")
                        self.is_logged_in = False
                        return False
                    
                   
                    
                except Exception as e:
                    self.logger.warning(f"检测登录状态时出错: {str(e)}")
                    await asyncio.sleep(1)
                    continue
               
                await asyncio.sleep(1)
                    
        except Exception as e:
            self.logger.error(f"检查登录状态失败: {str(e)}")
            return False
    
    async def search_profiles(self, search_params: LinkedInSearchParams) -> List[LinkedInProfile]:
        """搜索用户档案"""
        try:
            self.logger.info(f"🕵️‍♂️ 采集linkedin数据", extra={"show_in_ui": 1, "task_id": self.task_id})
            # 构建搜索URL
            search_url = f"https://www.linkedin.com/search/results/people/?keywords={search_params.keyword}&origin=SWITCH_SEARCH_VERTICAL"
            if search_params.location:
                search_url += f"&geoUrn=%5B%22{search_params.location}%22%5D"
            
            self.logger.info(f"访问搜索页面: {search_url}")
            # 访问搜索页面
            await self.page.goto(search_url)
            await asyncio.sleep(3)
            try:
                await self.page.wait_for_load_state("domcontentloaded", timeout=10000)  # 10秒超时
            except Exception:
                # 如果domcontentloaded超时，继续执行
                pass
            
            profiles = []
            current_page = 1
            
            # 动态获取最大页数
            max_pages = search_params.max_pages
          
            
            while current_page <= max_pages:
                # 等待搜索结果加载
                await self.page.wait_for_selector('[data-view-name="people-search-result"]', timeout=10000)
                
                # 提取用户信息
                profile_elements = await self.page.query_selector_all("a[href*='/in/']:not([data-view-name='search-result-lockup-title'])")
                
                for element in profile_elements:
                    try:
                        profile = await self._extract_profile_info(element)
                        if profile:
                            # 点击进入用户详情页面（新标签页）
                            await self._open_profile_detail_page(element, profile)
                            profiles.append(profile)
                            await asyncio.sleep(random.uniform(3, 10))
                    except Exception as e:
                        self.logger.warning(f"提取用户信息失败: {str(e)}")
                        continue
                    

                # 如果有找到profiles，批量保存到数据库
                if profiles:
                    # 将LinkedInProfile对象列表转换为字典列表
                    profiles_data = [profile.dict() for profile in profiles]
                    
                    # 正确使用数据库会话
                    async for db_session in db_manager.get_session():
                        try:
                            user_profile_repo = UserProfileRepository(db_session)
                            saved_profiles = await user_profile_repo.create_many(profiles_data)
                            print(f"成功保存 {len(saved_profiles)} 个用户档案")
                            break  # 成功后退出循环
                        except Exception as e:
                            print(f"保存用户档案失败: {str(e)}")
                            await db_session.rollback()
                            raise   
                # 检查是否还有下一页
                if current_page < max_pages:
                    next_button = await self.page.query_selector("button[aria-label='下一页']")
                    if next_button:
                        # 检查按钮是否可点击（不是disabled状态）
                        is_disabled = await next_button.evaluate("el => el.disabled")
                        if not is_disabled:
                            await next_button.click()
                            try:
                                await self.page.wait_for_load_state("domcontentloaded", timeout=10000)  # 10秒超时
                            except Exception:
                                # 如果domcontentloaded超时，继续执行
                                pass
                           
                            await asyncio.sleep(random.uniform(3, 10))
                            current_page += 1
                        else:
                            self.logger.info("下一页按钮已禁用，已到达最后一页")
                            break
                    else:
                        self.logger.info("未找到下一页按钮，已到达最后一页")
                        break
                else:
                    break
            self.logger.info(f"🕵️‍♂️ 采集linkedin数据完成,找到{len(profiles)}个用户", extra={"show_in_ui": 1, "task_id": self.task_id})
    
            return {'status':'success','step':{'profiles_found':len(profiles)},'results':profiles}
            
        except Exception as e:
            self.logger.error(f"搜索用户档案失败: {str(e)}")
            return {'status':'failed','step':{'error':str(e)},'results':[]}
    
    async def _open_profile_detail_page(self, element, profile: LinkedInProfile) -> None:
        """在新标签页中打开用户详情页面并提取详细信息"""
        try:
            # 监听新页面创建事件
            new_page_promise = self.context.wait_for_event('page')
            
            # 使用Ctrl+Click在新标签页打开
            await element.click(modifiers=['Control'])
            
            # 等待新页面创建
            new_page = await new_page_promise
            await new_page.wait_for_load_state('domcontentloaded')
            
            self.logger.info(f"已在新标签页打开用户详情: {profile.name}")
            
          
            
            # 提取详细信息
            await self._extract_profile_details(new_page, profile)

              # 提取联系信息
            await self._extract_contact_info(new_page, profile)
            
            # 关闭新页面
            await new_page.close()
            
        except Exception as e:
            import traceback
            self.logger.warning(f"打开用户详情页面失败: {str(e)}\n调用栈:\n{traceback.format_exc()}")
            try:
                await new_page.close()
            except Exception as e:
                self.logger.warning(f"关闭新页面失败: {str(e)}")

    async def _extract_contact_info(self, page, profile: LinkedInProfile) -> None:
        """点击Contact info并提取联系信息"""
        try:
            # 查找并点击"Contact info"文本元素
            contact_info_element = await page.query_selector("text=Contact info")
            if not contact_info_element:
                # 尝试其他可能的选择器
                contact_info_element = await page.query_selector("button:has-text('Contact info')")
            
            if contact_info_element:
                # 点击Contact info
                await contact_info_element.click()
                await page.wait_for_timeout(1000)  # 等待模态框加载
                
                self.logger.info("已点击Contact info，开始提取联系信息")
                
                # 提取联系信息
                contact_data = {}
                
                # 查找Email
                email_h3 = await page.query_selector("h3:has-text('^Email$')")
                if email_h3:
                    # 获取h3的下一个兄弟节点div
                    email_div = await email_h3.evaluate_handle(
                        """(node) => {
                            let el = node.nextElementSibling;
                            while (el && el.tagName !== 'DIV') el = el.nextElementSibling;
                            return el;
                        }"""
                    )
                    if email_div:
                        email_text = await email_div.text_content()
                        if email_text:
                            profile['email'] = email_text.strip()
                            self.logger.info(f"找到邮箱: {profile['email']}")
                        else:
                            self.logger.info("Email div存在但无内容")
                    else:
                        self.logger.info("未找到Email对应的div节点")
                else:
                    self.logger.info("未找到Email的h3标题")
                
                # 查找Website
                website_h3 = await page.query_selector("h3:has-text('^Website$')")
                if website_h3:
                    # 获取h3的下一个兄弟节点div
                    website_div = await website_h3.evaluate_handle(
                        """(node) => {
                            let el = node.nextElementSibling;
                            while (el && el.tagName !== 'DIV') el = el.nextElementSibling;
                            return el;
                        }"""
                    )
                    if website_div:
                        website_text = await website_div.text_content()
                        if email_text:
                            profile['website'] = website_text.strip()
                            self.logger.info(f"找到网站: {profile['website']}")
                        else:
                            self.logger.info("Website div存在但无内容")
                    else:
                        self.logger.info("未找到Website对应的div节点")
                else:
                    self.logger.info("未找到Website的h3标题")
                

              
                
                
             

                    
            else:
                self.logger.info("未找到Contact info按钮")
                
        except Exception as e:
            self.logger.warning(f"提取联系信息失败: {str(e)}")
    
    async def _extract_profile_details(self, page, profile: LinkedInProfile) -> None:
        """从用户详情页面提取Experience和Education信息"""
        try:
            # 提取Experience信息
            experience_data = await self._extract_experience(page)
            if experience_data:
                profile.experience = experience_data
                self.logger.info(f"提取到 {len(experience_data)} 条工作经历")
            
            # 提取Education信息
            education_data = await self._extract_education(page)
            if education_data:
                profile.education = education_data
                self.logger.info(f"提取到 {len(education_data)} 条教育背景")

        except Exception as e:
            self.logger.warning(f"提取用户详细信息失败: {str(e)}")
    
    async def _extract_experience(self, page) -> Optional[List[Dict[str, Any]]]:
        """提取工作经历信息"""
        try:
            # 查找Experience标题元素
            # 先找到id="experience"的div元素
            experience_section = await page.query_selector("div#experience")
            if not experience_section:
                return None

            # 获取该div的第二个兄弟元素，然后在其下查找ul
            # 先定位到第二个兄弟元素
            second_sibling = await experience_section.evaluate_handle(
                """node => {
                    let sib = node.nextElementSibling;
                    if (!sib) return null;
                    sib = sib.nextElementSibling;
                    return sib || null;
                }"""
            )
            if not second_sibling:
                return None

            # 在第二个兄弟元素下查找ul
            experience_list = await second_sibling.query_selector("ul")
            if not experience_list:
                return None
            
            # 提取所有li元素
            experience_items = await experience_list.query_selector_all("li")
            experiences = []
            
            for item in experience_items:
                try:
                    # 提取工作经历信息，并去重
                    item_text = await item.inner_text()
                    clean_text = item_text.strip()
                    if clean_text:
                        # 检查是否已存在相同的text
                        if not any(exp["text"] == clean_text for exp in experiences):
                            experiences.append({
                                "text": clean_text,
                            })
                except Exception as e:
                    self.logger.warning(f"提取单个工作经历失败: {str(e)}")
                    continue
            
            return experiences if experiences else None
            
        except Exception as e:
            self.logger.warning(f"提取工作经历失败: {str(e)}")
            return None
    
    async def _extract_education(self, page) -> Optional[List[Dict[str, Any]]]:
        """提取教育背景信息"""
        try:
            # 查找Education标题元素
            education_section = await page.query_selector("div#education")
            if not education_section:
                return None
            
            # 获取该div的第二个兄弟元素，然后在其下查找ul
            # 先定位到第二个兄弟元素
            second_sibling = await education_section.evaluate_handle(
                """node => {
                    let sib = node.nextElementSibling;
                    if (!sib) return null;
                    sib = sib.nextElementSibling;
                    return sib || null;
                }"""
            )
            if not second_sibling:
                return None

            # 在第二个兄弟元素下查找ul
            education_list = await second_sibling.query_selector("ul")
            if not education_list:
                return None
            
            # 提取所有li元素
            education_items = await education_list.query_selector_all("li")
            educations = []
            
            for item in education_items:
                try:
                    # 提取教育背景信息
                    item_text = await item.inner_text()
                    if item_text.strip():
                        educations.append({
                            "text": item_text.strip(),
                        })
                except Exception as e:
                    self.logger.warning(f"提取单个教育背景失败: {str(e)}")
                    continue
            
            return educations if educations else None
            
        except Exception as e:
            self.logger.warning(f"提取教育背景失败: {str(e)}")
            return None
    
    async def _extract_profile_info(self, element) -> Optional[LinkedInProfile]:
        """从页面元素提取用户信息"""
        try:
            # 提取姓名和链接 - 根据新的HTML结构
            name_element = await element.query_selector("a[data-view-name='search-result-lockup-title']:not([href='#'])")
           
            if not name_element:
                return None
            
            name = await name_element.inner_text()
            profile_url = await name_element.get_attribute("href")
            
            # 提取职位 - 基于name_element的父级p元素的下一个兄弟节点
            title_element = None
            if name_element:
                # 找到name_element的父级p元素
                parent_p = await name_element.query_selector("xpath=..")
                if parent_p:
                    # 检查父元素是否为p标签
                    tag_name = await parent_p.evaluate("el => el.tagName.toLowerCase()")
                    if tag_name == 'p':
                        # 获取该p元素的下一个兄弟节点
                        title_element = await parent_p.query_selector("xpath=following-sibling::p[1]")
            
            title = await title_element.inner_text() if title_element else ""
            
            # 提取公司 - 在element下面的div里面的p标签
            company_element = await element.query_selector("div p:has-text('Current:')")
            if not company_element:
                company_element = await element.query_selector("div p:has-text('Past:')")
           
            
            company = await company_element.inner_text() if company_element else ""
            job_title = title
            
            # 提取位置 - 基于name_element的第2个兄弟节点
            location_element = None
            if name_element:
                # 获取name_element的第2个兄弟节点
                parent_p = await name_element.query_selector("xpath=..")
                if parent_p:
                    # 检查父元素是否为p标签
                    tag_name = await parent_p.evaluate("el => el.tagName.toLowerCase()")
                    if tag_name == 'p':                       
                        location_element = await parent_p.query_selector("xpath=following-sibling::p[2]")
            
            location = await location_element.inner_text() if location_element else ""
            
            # 提取头像 - 使用更稳定的选择器
            avatar_element = await element.query_selector("img[src*='media.licdn.com']")
         
            
            avatar_url = ""
            if avatar_element:
                # 检查元素类型
                tag_name = await avatar_element.evaluate("el => el.tagName.toLowerCase()")
                if tag_name == 'img':
                    avatar_url = await avatar_element.get_attribute("src")
                else:
                    aria_label = await avatar_element.get_attribute("aria-label")
                    if aria_label:
                        avatar_url = f"avatar_{aria_label}"
            
            # 生成profile_id
            profile_id = profile_url.split("/")[-1] if profile_url else str(uuid.uuid4())
            
             # 提取技能 - 在element下面的div里面的p标签
            skills_element = await element.query_selector("div p:has-text('Skills:')")
            skills = await skills_element.inner_text() if skills_element else ""
             # 提取技能 - 在element下面的div里面的p标签
            summary_element = await element.query_selector("div p:has-text('Summary:')")
            summary = await summary_element.inner_text() if summary_element else ""

            return LinkedInProfile(
                profile_id=profile_id,
                name=name.strip(),
                title=job_title.strip() if job_title else "",
                company=company.replace("Current:", "").replace("Past:", "").strip() if company else "",
                address=location.strip() if location else "",
                profile_url=profile_url,
                avatar_url=avatar_url if avatar_url else "",
                skills=skills.strip() if skills else "",
                summary=summary.strip() if summary else ""

            )
            
        except Exception as e:
            self.logger.warning(f"提取用户信息失败: {str(e)}")
            return None

    async def run_task(self, task_id: str, **kwargs) -> Dict[str, Any]:
        """运行任务"""
        try:
            self.logger.info(f"开始执行任务: {task_id}")
            
            # 启动浏览器
            if not await self.start_browser():
                raise Exception("浏览器启动失败")
            
            # 检查登录状态
            if not await self.check_login_status():
                self.logger.info("未登录，开始登录流程")
                # 等待用户手动登录
                if not await self.login():
                    raise Exception("登录失败或超时")
            
            # 执行搜索任务
            search_params = LinkedInSearchParams(**kwargs)
            result = await self.search_profiles(search_params)
            
           
            self.logger.info(f"任务完成: {task_id}")
            return result
            
        except Exception as e:
            self.logger.error(f"任务失败: {task_id}, 错误: {str(e)}")
            return {
                "task_id": task_id,
                "status": "failed",
                "step": {'error':str(e)},
            }
    
    async def close(self):
        """关闭客户端"""
        await self.close_browser()


